package jmine.tec.rpc.common.serialization.json;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;

import jmine.tec.json.JSON;
import jmine.tec.json.JSONObject;
import jmine.tec.json.JSONString;
import jmine.tec.rpc.RPCMessages;
import jmine.tec.rpc.common.RPCException;

import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.commons.beanutils.PropertyUtils;

/**
 * Conversor de JSON para Java e vice-versa
 * 
 * @author lundberg
 */
public class JSONBeanConverter extends AbstractJSONObjectConverter<Object> implements JSONConverterStrategy {

    private static final String BEAN_CLASS = "&class";

    /**
     * {@inheritDoc}
     */
    public boolean matchesJava(Object object) {
        return object != null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected JSON objectType() {
        return new JSONString("bean");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object instantiate(JSONConversionContext context, JSON json) {
        try {
            Class objectClass = (Class) context.toJava(json.getProperty(BEAN_CLASS));
            return ConstructorUtils.invokeConstructor(objectClass, new Object[0]);
        } catch (Exception e) {
            throw new RPCException(RPCMessages.JSON_ERROR_CREATING_BEAN.create(), e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void fillObject(JSONConversionContext context, JSON json, Object object) {
        try {
            for (String property : json.getPropertyNames()) {
                if (!this.isSpecialProperty(property)) {
                    PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(object, property);
                    descriptor.getWriteMethod().invoke(object, context.toJava(json.getProperty(property)));
                }
            }
        } catch (Exception e) {
            throw new RPCException(RPCMessages.JSON_ERROR_CREATING_BEAN.create(), e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void fillJSON(JSONConversionContext context, Object object, JSONObject json) {
        json.setProperty(BEAN_CLASS, context.toJSON(object.getClass()));
        for (PropertyDescriptor property : PropertyUtils.getPropertyDescriptors(object)) {
            if (this.isBeanProperty(property)) {
                json.setProperty(property.getName(), context.toJSON(this.extractValue(object, property)));
            }
        }
    }

    /**
     * Extrai o valor da propriedade do objeto
     * 
     * @param object object
     * @param property property
     * @return Object
     */
    private Object extractValue(Object object, PropertyDescriptor property) {
        try {
            return property.getReadMethod().invoke(object);
        } catch (IllegalArgumentException e) {
            throw new RPCException(RPCMessages.SERIALIZATION_JSON_ERROR_READING_PROPERTY.create(object.getClass().getName(),
                    property.getName(), e.getMessage()), e);
        } catch (IllegalAccessException e) {
            throw new RPCException(RPCMessages.SERIALIZATION_JSON_ERROR_READING_PROPERTY.create(object.getClass().getName(),
                    property.getName(), e.getMessage()), e);
        } catch (InvocationTargetException e) {
            throw new RPCException(RPCMessages.SERIALIZATION_JSON_ERROR_READING_PROPERTY.create(object.getClass().getName(),
                    property.getName(), e.getMessage()), e);
        }
    }

    /**
     * @param descriptor descriptor
     * @return boolean
     */
    private boolean isBeanProperty(PropertyDescriptor descriptor) {
        return descriptor.getWriteMethod() != null && descriptor.getReadMethod() != null;
    }
}
